/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.validator.internal;

import java.util.*;
import java.util.Map.Entry;
import ch.msoftch.internal.*;
import ch.simpleel.*;
import ch.simpleel.accessors.*;
import ch.simpleel.util.*;
import ch.trackedbean.validator.*;

/**
 * Validation engine.
 * 
 * @author M. Hautle
 */
public class Validator implements IValidator {
    /** Return value of {@link #getValue(String, Object)} if no getter for the specified property is registed. */
    protected static final Object NO_GETTER = new Object();

    /** The type for which gets validated by this object. */
    protected final Class<?> type;

    /** The business validators of this validator. */
    protected List<IBusinessValidator> businessValidators = new ArrayList<IBusinessValidator>(1);

    /** The validators mapped on their property names. */
    protected Map<String, List<IPropertyValidator>> validators = new HashMap<String, List<IPropertyValidator>>();

    /** The accessors for the properties of {@link #simpleProperties}. */
    protected Map<String, IValueAccessor> getters = new HashMap<String, IValueAccessor>();

    /**
     * List holding the names of the simple properties to validate.<br>
     * This list must have the same content as the key set of {@link #validators}
     */
    protected List<String> simpleProperties = new ArrayList<String>();

    /** List holding the {@link DepthValidator}s of this validator. */
    protected List<DepthValidator> depthValidationProperties = new ArrayList<DepthValidator>();

    /**
     * Default constructor.
     * 
     * @param type The type to validate
     * @throws ValidatorInitialisationException If something went wrong
     */
    public Validator(Class<?> type) throws ValidatorInitialisationException {
        this.type = type;
    }

    /**
     * Adds a business validator.<br>
     * The passed business validator will only be executed if the property validations were error free!
     * 
     * @param val The validator to add
     */
    public void addBusinessValidator(IBusinessValidator val) {
        businessValidators.add(val);
    }

    /**
     * Adds a property for deep validation.<br>
     * This is used for properties holding beans which have to be validated too.
     * 
     * @param property The property
     * @param message The message for an invalid property
     * @return True if the property was added for deep validation
     */
    public boolean addDeepValidation(String property, String message) {
        final IValueAccessor read = ValueAccessorManager.getPropertyAccessor(type, property);
        if (!read.isReadable())
            return false;
        depthValidationProperties.add(new DepthValidator(property, message, read));
        return true;
    }

    /**
     * Adds the given validator for the given property.
     * 
     * @param property The property
     * @param validator The validator to add
     * @return True if the validator was added
     */
    public boolean addPropertyValidation(String property, IPropertyValidator validator) {
        if (!registerSimpleProperty(property))
            return false;
        getValidatorList(property).add(validator);
        return true;
    }

    /**
     * Returns the list holding the validators of the given property.<br>
     * A new list gets created if necessary.
     * 
     * @param property The property
     * @return The list holding the validators
     */
    private List<IPropertyValidator> getValidatorList(String property) {
        List<IPropertyValidator> vals = validators.get(property);
        if (vals == null)
            validators.put(property, vals = new ArrayList<IPropertyValidator>(1));
        return vals;
    }

    /**
     * Registers the given property as "property to validate".<br>
     * This method adds the getter method of the given property to {@link #getters} if necessary.
     * 
     * @param property The property
     * @return True if the property can be validated
     */
    private boolean registerSimpleProperty(String property) {
        if (getters.containsKey(property))
            return true;
        final IValueAccessor acc = ValueAccessorManager.getPropertyAccessor(type, property);
        if (!acc.isReadable())
            return false;
        getters.put(property, acc);
        simpleProperties.add(property);
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public boolean doesSomething() {
        return simpleProperties.size() + depthValidationProperties.size() + businessValidators.size() > 0;
    }

    /**
     * {@inheritDoc}
     */
    public boolean doesBusinessValidation() {
        return !businessValidators.isEmpty();
    }

    /**
     * {@inheritDoc}
     */
    public ValidationResult validate(Object bean) throws TechnicalException {
        final ValidationContext context = new ValidationContext(bean);
        validate(bean, context);
        return context.getResult();
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public ValidationResult validateBusinessPart(Object bean) {
        final ValidationContext context = new ValidationContext(bean);
        for (IBusinessValidator v : businessValidators)
            context.addGeneralError(v.validate(bean, context));
        return context.getResult();
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public boolean validate(Object bean, IValidationContext context) throws TechnicalException {
        boolean err = false;
        // validate the simple properties
        for (String prop : simpleProperties)
            err |= context.addErrors(prop, validate(prop, bean));
        // validate the business content
        for (IBusinessValidator v : businessValidators)
            err |= context.addGeneralError(v.validate(bean, context));
        // validate the structure properties
        for (DepthValidator prop : depthValidationProperties)
            err |= prop.validate(bean, context);
        return err;
    }

    /**
     * {@inheritDoc}
     */
    public IErrorDescription[] validate(String property, Object bean) throws TechnicalException {
        final Object value = getValue(property, bean);
        if (value == NO_GETTER)
            return null;
        IErrorDescription[] err = null;
        for (IPropertyValidator v : validators.get(property))
            err = appendError(err, v.validate(value));
        return err;
    }

    /**
     * Returns the value of the given property
     * 
     * @param property The propertyname
     * @param bean The bean to invoke
     * @return The value of the given property or {@link #NO_GETTER} if the property is not registred
     * @throws TechnicalException If something went wrong
     */
    protected Object getValue(String property, Object bean) throws TechnicalException {
        try {
            final IValueAccessor<?> get = getters.get(property);
            if (get == null)
                return NO_GETTER;
            return get.getValue(bean, null);
        } catch (Exception e) {
            throw new TechnicalException("Error while reading " + property + " from " + bean, e);
        }
    }

    /**
     * Appends the error to the {@link IErrorDescription} array.
     * 
     * @param err A {@link IErrorDescription} array or null
     * @param errorDescription The error to append or null
     * @return The resulting array
     */
    protected IErrorDescription[] appendError(IErrorDescription[] err, IErrorDescription errorDescription) {
        if (errorDescription == null)
            return err;
        if (err == null)
            return new IErrorDescription[] { errorDescription };
        return MiscUtils.arrayAppend(err, errorDescription);
    }

    /**
     * Appends the information from the passed validator to this one.
     * 
     * @param val The validator
     */
    public void appendValidatorInformation(Validator val) {
        // append the validators
        for (Entry<String, List<IPropertyValidator>> e : val.validators.entrySet()) {
            final String property = e.getKey();
            if (!registerSimpleProperty(property))
                continue;
            final List<IPropertyValidator> list = getValidatorList(property);
            for (IPropertyValidator v : e.getValue())
                list.add(v);
        }
        // append the depth properties
        depthValidationProperties.addAll(val.depthValidationProperties);
        // append the business validators
        businessValidators.addAll(val.businessValidators);
    }

    /**
     * Appends the simple validators for the given property from the passed validator to this one.
     * 
     * @param val The validator
     * @param srcProperty The property from which to inherit (on the passed validator)
     * @param dstProperty The property where to append the inherited validations (on this validator)
     */
    public void appendSimpleValidators(Validator val, String srcProperty, String dstProperty) {
        final List<IPropertyValidator> validators = val.validators.get(srcProperty);
        if (validators == null || !registerSimpleProperty(dstProperty))
            return;
        // add the validators
        final List<IPropertyValidator> list = getValidatorList(dstProperty);
        for (IPropertyValidator v : validators)
            list.add(v);
    }
}
